using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace UTP
{
    public class Package : Protocol
    {
        /// <summary>
        /// 包结构
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public class Packet : IComparable<Packet>
        {
            /// <summary>
            /// 包业务标志
            /// </summary>
            public byte Service { get; set; }
            /// <summary>
            /// 包序号
            /// </summary>
            public byte id { get; set; }
            /// <summary>
            /// 消息编号
            /// </summary>
            public long Index { get; set; }
            /// <summary>
            /// 数据总长度
            /// </summary>
            public long Size { get; set; }
            /// <summary>
            /// 分包总数
            /// </summary>
            public long Count { get; set; }
            /// <summary>
            /// 数据包
            /// </summary>
            public byte[] Data { get; set; }
            public byte[] Checksum { get; set; } = new byte[] { 0x00, 0x01 };

            /// <summary>
            /// 序列化为BYTES
            /// </summary>
            /// <returns></returns>
            public byte[] ToBytes()
            {
                object _lock = new object();
                List<byte> _buffer = new List<byte>();
                lock( _lock )
                {
                    _buffer.Add(this.Service); //1bit
                    _buffer.Add(this.id); //1bit
                    _buffer.AddRange(LongToBytes(this.Size)); //8bits
                    _buffer.AddRange(LongToBytes(this.Count));//8bits
                    _buffer.AddRange(LongToBytes(this.Index)); //8bits
                    _buffer.AddRange(this.Data);//数据 1400
                    _buffer.AddRange(CRC16(this.Data));//1bits
                }
                
                return _buffer.ToArray();

            }
            /// <summary>
            /// 载入数据转换为PACKET
            /// </summary>
            public byte[] LoadData
            {
                set
                {
                    if (value.Length < 28) return;//小于包头
                    

                    this.Service = value[0];
                    this.id = value[1];
                    this.Size = BytesToLong(value.Skip(2).Take(8).ToArray());
                    this.Count = BytesToLong(value.Skip(10).Take(8).ToArray());
                    this.Index = BytesToLong(value.Skip(18).Take(8).ToArray());
                    this.Data = value.Skip(26).Take(value.Length - 28).ToArray();
                    this.Checksum = value.Skip(value.Length - 2).ToArray();
                    var _crc = CRC16(this.Data);
                    if (string.Format("{0:X2}{1:X2}", _crc[0], _crc[1]) != string.Format("{0:X2}{1:X2}", this.Checksum[0], this.Checksum[1]) )
                    {
                        this.sucess = false;
                        Console.WriteLine("CRCSUM ERROR:{0}!={1},Data Length={2},packet:{3}/{4},size:{5}",
                            string.Format("{0:X2}{1:X2}", _crc[0], _crc[1]),
                            string.Format("{0:X2}{1:X2}", this.Checksum[0], this.Checksum[1]),
                            this.Data.Length,
                            this.Index,
                            this.Count,
                            this.Size
                            );
                    }
                }
            }
            public bool sucess { get; set; } = true;
            /// <summary>
            /// 内部比较器
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            public int CompareTo(Packet other)
            {
                //if(other == null) return 1;
                if (this.Index != other.Index)
                {
                    return this.Index.CompareTo(other.Index);
                }
                else return 0;
            }
        }
        /// <summary>
        /// 分包大小
        /// </summary>
        public const int _defaultPartSize = 1470;//1500-28
        public static int _id = 0;
        /// <summary>
        ///  是否启用GZIP
        /// </summary>
        public static bool _zip { get; set; } = true;
        /// <summary>
        /// 分包
        /// </summary>
        /// <param name="datagram">数据包（使用默认块大小：1024 byte)</param>
        /// <returns>分包列表</returns>
        public static List<Packet> Split(byte[] datagram)
        {
            if (datagram == null)
                return null;

            if (_zip)
                datagram = Gzip(datagram);

            List<Packet> packets = new List<Packet>();
            object _lock = new object();
            int _length = datagram.Length;//数据总长度
            byte _packetid = GetID();
            if (_length <= _defaultPartSize)
            {
                packets.Add(new Packet()
                {
                    Service = 0x00,
                    id = _packetid,
                    Count = 1,
                    Size = _length,
                    Index = 0,
                    Data = datagram,
                    //Checksum = CRC16Helper.GetCRC16(datagram, false)
                });
                return packets;
            }

            var _size = (float)_length / (float)_defaultPartSize;
            int counts = (int)Math.Ceiling(_size);
            //Console.WriteLine("SPLIT:文件大小：{0}，包尺寸：{1},预计拆分：{2}",_length, _defaultPartSize, counts);
            var Loop = Parallel.For(0, counts, async i =>
            {
                int _start = _defaultPartSize * i;
                byte[] _buffer;
               
                    int _size = _defaultPartSize;
                    if (_length - _defaultPartSize * i < _defaultPartSize)
                        _size = _length - _start;

                     _buffer = datagram.Skip(_start).Take(_size).ToArray();
               
                  //new byte[_size];

                //Console.WriteLine("添加包:{0:D4},ID:{1},SIZE:{2},COUNTS:{3},LEN:{4}",i, _packetid, _length, counts, _buffer.Length);
                lock (_lock)
                {
                    packets.Add(new Packet()
                    {
                        Service = 0x00,
                        id = _packetid,
                        Size = _length,
                        Count = counts,
                        Index = i,
                        Data = _buffer,
                        //Checksum = CRC16Helper.GetCRC16(_buffer, false)
                    });
                }
                //Thread.Sleep(0);
            });


            //Console.WriteLine("拆分结果：{0}个包,运行{1}次",packets.Count,c);

            packets.Sort();
            return packets;
        }
        /// <summary>
        /// 合并包
        /// </summary>
        /// <param name="_packet"></param>
        /// <returns></returns>
        public static byte[] Combine(List<Packet> _packet)
        {
            List<byte> _buffer = new List<byte>();
            for(var i=0; i< _packet.Count; i++)
            {
                _buffer.AddRange(_packet[i].Data);
            }
            var data = _buffer.ToArray();
            if (_zip && _packet.Count + 1 == _packet[0].Count)
                data = UnGzip(data);
            return data;
        }
        /// <summary>
        /// 数字转BYTE[]
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static byte[] LongToBytes(long value, int size = 8)
        {
            var tempbuffer = new List<byte>();
            var str64 = value.ToString("X");//.PadLeft(2,'0');
            if (str64.Length % 2 != 0) { str64 = "0" + str64; }
            var len = str64.Length / 2;
            var src = new byte[len];

            for (int i = 0; i < size - len; i++)
                tempbuffer.Add(0x00);

            for (int i = 0; i < len; i++)
                tempbuffer.Add(byte.Parse(str64.Substring(i * 2, 2), NumberStyles.HexNumber));

            return tempbuffer.ToArray();
        }
        /// <summary>
        /// BYTE[]转数字
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        protected static long BytesToLong(byte[] src)
        {
            var str64 = "";
            for (int i = 0; i < src.Length; i++)
                str64 += src[i].ToString("X2");
            //Console.WriteLine(str64);
            return long.Parse(str64, NumberStyles.HexNumber);
        }

        /// <summary>
        /// 获取随机包
        /// </summary>
        /// <returns></returns>
        public static byte[] GetRandomPacket(int size=200)
        {
            byte[] packet = new byte[size];
            Random rand = new Random();
            rand.NextBytes(packet);
            return packet;
        }

        public static byte GetID()
        {
            if (_id >= 255)
            {
                _id = 0;
            }
            else
            {
                _id++;
            }
            return Convert.ToByte( _id );
        }
        /// <summary>
        /// GZIP压缩
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static byte[] Gzip(string s)
        {
            byte[] rawData = Encoding.UTF8.GetBytes(s);
            return Gzip(rawData);
        }
        /// <summary>
        /// GZIP压缩
        /// </summary>
        /// <param name="rawData"></param>
        /// <returns></returns>
        public static byte[] Gzip(byte[] rawData)
        {

            using (MemoryStream ms = new MemoryStream())
            {
                using (GZipStream zip = new GZipStream(ms, CompressionMode.Compress, true))
                {
                    zip.Write(rawData, 0, rawData.Length);
                    zip.Close();

                    return ms.ToArray();
                }
            }
        }
        /// <summary>
        /// GZIP解压
        /// </summary>
        /// <param name="rawData"></param>
        /// <returns></returns>
        public static string UnGzipToString(byte[] rawData)
        {
            byte[] _unzip = UnGzip(rawData);
            return UTF8Encoding.UTF8.GetString(_unzip);
        }
        /// <summary>
        /// GZIP解压
        /// </summary>
        /// <param name="rawData"></param>
        /// <returns></returns>
        public static byte[] UnGzip(byte[] rawData)
        {

            using (MemoryStream ms = new MemoryStream(rawData))
            {
                using (GZipStream zs = new GZipStream(ms, CompressionMode.Decompress))
                {
                    byte[] buffer = new byte[1024];
                    using (MemoryStream buf = new MemoryStream())
                    {
                        try
                        {
                            for (int offset; (offset = zs.Read(buffer, 0, buffer.Length)) > 0;)
                                buf.Write(buffer, 0, offset);

                            return buf.ToArray();
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("UNGZIP ERROR：{0}", ex.ToString());
                        }
                        return null;
                    }

                }
            }
        }
        public static byte[] CRC16(byte[] data)
        {
            int len = data.Length;
            if (len > 0)
            {
                ushort crc = 0xFFFF;

                for (int i = 0; i < len; i++)
                {
                    crc = (ushort)(crc ^ (data[i]));
                    for (int j = 0; j < 8; j++)
                    {
                        crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
                    }
                }
                byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                byte lo = (byte)(crc & 0x00FF);         //低位置

                return new byte[] { lo, hi };
            }
            return new byte[] { 0, 0 };
        }
    }
}